package com.googlecode.groovy_toy_orm



/**
 *
 * @author: DavydovMD
 * Date: 09.04.13
 * Time: 18:55
 */
class BaseProvider<T> {
    Class<T> clazz;
    /**
     *
     * @param field MUST accept NULL as (default) value
     * @return
     */
    MultiTableMapper mapper


    public BaseProvider(Class<T> clazz, List<SingleDbTableMapper> mappers) {
        this.clazz = clazz
        this.mapper = new MultiTableMapper(mappers, clazz)
    }

    public BaseProvider(Class<T> clazz, MultiTableMapper mapper) {
        this.clazz = clazz
        this.mapper = mapper
    }

    public QueryBuilder query() {
        mapper.queryBuilder()
    }

    /**
     * it's supposed that clazz have a constructor with one argument - id throwing NoSuchElement exception if there is no such Id
     * @param id
     * @return Object by Id if found, null otherwise.
     */

    public T byId(def id) {
        try {
            mapper.buildFromDb(id) as T
        } catch (NoSuchElementException ignored) {
            null
        }
    }

    public List<T> byIds(List ids) {
        List<T> res = []
        if (!mapper.isMultiColumn(mapper.keyField)) {
            List<Map> rows = query().allFields().where(mapper.keyField, ConditionOp.IN, ids).build().execute()
            rows.each { Map row ->
                res << mapper.buildFromTranformed(row, clazz) // It's assumed that
            }
        } else {
            //Multi-column values are not compatible with IN operator!
            ids.each {
                res << byId(it)
            }
        }
        res
    }

    /**
     * Important: eliminates duplicates!!!
     * @param column
     * @param rows
     * @return
     */
    public static <T> List<T> getColumnValues(String column, List<Map> rows) {
        Set res = new HashSet()
        rows.each {
            res << it.get(column)
        }
        new ArrayList(res)
    }

    //Might be used in some providers
    protected static <T> List<T> getFieldValues(String field, List<Map> rows, Mapper mapper) {
        Set res = new HashSet()
        rows.each {
            Map<String, Object> row ->
                res << row.find {
                    (it.key == field) || (Util.lastToken(it.key) == Util.lastToken(field))
                }.getValue()
        }
        new ArrayList(res)

    }

    public <T> List<T> getFieldValues(String field, List<Map> rows) {
        getFieldValues(field, rows, mapper)
    }

    /**
     *
     * @param field
     * @param value
     * @return
     */
    public List<T> byFieldValue(String field, Object value) {
        Object compatibleValue = SqlMappingUtil.toCompatible(value)

        def qEx = query().distinct().field(mapper.getKeyField()).where(field, ConditionOp.EQ, compatibleValue).build()

        List<Map> rows = qEx.execute()
        List ids = getColumnValues(mapper.getKeyField(), rows)
        byIds(ids)
    }


    public List getFieldValuesUsingFilters(String field, List<Condition> filters) {
        WhereClauseBuilder wBuilder = query().distinct().field(field).field(mapper.getKeyField()).where()
        filters.each { Condition filter ->
            wBuilder = wBuilder.and(filter)
        }

        List<Map> rows = wBuilder.build().execute()
        getColumnValues(field, rows)
    }

    public List findIdsUsingFilters(List<Condition> filters) {
        getFieldValuesUsingFilters(mapper.getKeyField(), filters)
    }

    public List<T> usingFilters(List<Condition> filters) {
        byIds(findIdsUsingFilters(filters))
    }

    /**
     * Returns
     * Appropriate table and database is determined by field
     * @param fieldOfInterest - name of field to be used to determine database and table
     * @param query GString query that MUST return id as it's fist column.
     * @return List of objects found
     */
    public List<T> usingQuery(QueryExecutor queryExecutor) {
        List<Map> rows = queryExecutor.execute()
        byIds(getColumnValues(mapper.getKeyField(), rows))
    }

    public T usingQueryFirst(QueryExecutor queryExecutor) {
        Map row = queryExecutor.firstRow()
        if (row)
            byId(row.get(mapper.keyField))
        else
            null
    }



    public T byFieldValueFirst(String field, Object value) {
        Map row = query().distinct().field(mapper.getKeyField()).where(field, ConditionOp.EQ, value).build().firstRow()
        if (row)
            byId(row[mapper.getKeyField()])
        else
            null
    }


}
